﻿package ygg.printer
{
	import j.sus2maj7.printer.*;
	import j.sus2maj7.context.*;
	import j.sus2maj7.util.*;
	import flash.utils.ByteArray;
	
	import ygg.encoding.swf.structure.*;
	import ygg.encoding.swf.abc.*;
	import ygg.encoding.swf.abc.structure.*;
	import ygg.encoding.swf.abc.constant.*;
	import ygg.encoding.swf.tag.*;
	
	public class MethodInfoPrinter extends AbstractPrinter
	{
		public function MethodInfoPrinter()
		{
			super();
		}
		override public function printToString(o:*, context:IContext=null):String
		{
			return printToStringB(o, context).toString();
		}
		override public function printToStringB(o:*, context:IContext=null):StringBuilder
		{
			var mi:MethodInfo = o as MethodInfo;
			
			return print(mi, AbcContext(context.vars.abcContext), PrinterContext(context) );
		}
		private function format(mi:MethodInfo):String
		{
			var s:String = "";
			
			if (mi.flags & MethodFlag.NATIVE)
			{
				s = "native ";
			}
			
			s += (isPrivate(mi.name)?'private ': 'public ') + TraitKind.getName(mi.kind) + ' '+ mi.name + "(";
			
			var bHasParamNames:Boolean = false;
			if (mi.flags & MethodFlag.HAS_PARAM_NAMES) bHasParamNames = true;
			var bHasOptional:Boolean = false;
			if (mi.flags & MethodFlag.HAS_OPTIONAL) bHasOptional = true;
			
			var i:int = 0;
			var len:int = mi.paramTypes.length;
			for (i; i < len; i++)
			{
				if (i > 0) s += ', ';
				else s += ' ';
				if (bHasParamNames)
				{
					if (mi.paramNames[i] != undefined) s += mi.paramNames[i] + ':';
				}
				s += mi.paramTypes[i];
				
				if (bHasOptional)
				{
					if (mi.optionalValues[i] != undefined) s += '=' + mi.optionalValues[i];
				}
			}
			if (i > 0) s += ' ';
			
			s += "):" + mi.returnType;
			
			return s
		}
		private function print(mi:MethodInfo, context:AbcContext, pc:PrinterContext):StringBuilder
		{
			trace('mip print');
			var cPool:ConstantPool = context.constantPool;
			var sb:StringBuilder = (pc.sb == null) ? new StringBuilder() : pc.sb;
			var attr:String = pc.vars.attr || '';
			var indent:String = pc.indent || '';
			var tab:String = pc.tab || '    ';
			
			sb.append("\n/*starting mip*/\n");
			
			//do package?

			if (mi.metadata)
			{
				for each (var md:* in mi.metadata)
				{
					sb.append(indent + md + "\n")
				}
			}
trace('a');
			sb.append(indent + "// disp_id " + mi.id + "\n");trace('c');
			sb.append(indent + attr + format(mi) + "\n"); trace('d', mi.debugName, mi.name);//? attr?
			var code:AbcBinary;
			if (mi.methodBody) code = mi.methodBody.binary as AbcBinary;
trace('b');
			if (code != null)
			{
				sb.append(indent + "{\n");
				var oldIndent:String = indent;
				
				indent += tab;
				
				if (mi.flags & MethodFlag.NEED_ACTIVATION)
				{
					sb.append(indent + "activation!!!!!!!! {\n");
					//!mi.activation.dump(abc, indent + _tab, ""); //?.
					sb.append(indent + "}\n");
				}
				
				sb.append(indent + "// numLocals= " + mi.methodBody.numLocals +
					  "    initScope= " + mi.methodBody.initScopeDepth +
					  "    maxScope= " + mi.methodBody.maxScopeDepth +
					  "    maxStack= " + mi.methodBody.maxStack +
					  "    codeLen= " + code.length + "\n");
				
				code.position = 0
				
				var labels:LabelInfo = new LabelInfo(); //?
				
				while (code.bytesAvailable > 0)
				{
					var s:String = code.position.toString();
					
					while (s.length < 8)
					{
						s += ' ';
					}
					s = indent + s;
					var opcode:uint = code.readUnsignedByte()

					if (opcode == OpCode.OP_label || ((code.position - 1) in labels))
					{
						sb.append(indent + "\n");
						sb.append(indent + labels.labelFor(code.position - 1) + ": \n");
					}

					s += OpCode.opNames[opcode];
					
					s += OpCode.opNames[opcode].length < 8 ? "\t\t" : "\t";
						
					switch(opcode)
					{
						case OpCode.OP_debugfile:
						case OpCode.OP_pushstring:
							s += '"' + cPool.strings[code.readEncU32()].replace(/\n/g, "\\n").replace(/\t/g, "\\t") + '"';
							break
							
						case OpCode.OP_pushnamespace:
							s += cPool.namespaces[code.readEncU32()];
							break
							
						case OpCode.OP_pushint:
							var i:int = cPool.ints[code.readEncU32()];
							s += i + "\t// 0x" + i.toString(16);
							break
							
						case OpCode.OP_pushuint:
							var u:uint = cPool.uints[code.readEncU32()];
							s += u + "\t// 0x" + u.toString(16);
							break;
							
						case OpCode.OP_pushdouble:
							s += cPool.doubles[code.readEncU32()];
							break;
							
						case OpCode.OP_getsuper:
						case OpCode.OP_setsuper:
						case OpCode.OP_getproperty:
						case OpCode.OP_initproperty:
						case OpCode.OP_setproperty:
						case OpCode.OP_getlex:
						case OpCode.OP_findpropstrict:
						case OpCode.OP_findproperty:
						case OpCode.OP_finddef:
						case OpCode.OP_deleteproperty:
						case OpCode.OP_istype:
						case OpCode.OP_coerce:
						case OpCode.OP_astype:
						case OpCode.OP_getdescendants:
							s += cPool.multinames[code.readEncU32()]; //check for imports
							break;
							
						case OpCode.OP_constructprop:
						case OpCode.OP_callproperty:
						case OpCode.OP_callproplex:
						case OpCode.OP_callsuper:
						case OpCode.OP_callsupervoid:
						case OpCode.OP_callpropvoid:
							s += cPool.multinames[code.readEncU32()]
							s += " (" + code.readEncU32() + ")";
							break;
							
						case OpCode.OP_newfunction:
						{
							var method_id:int = code.readEncU32();
							s += context.methods[method_id];
							context.methods[method_id].anon = true; //anon
							break;
						}
						
						case OpCode.OP_callstatic:
							s += context.methods[code.readEncU32()];
							s += " (" + code.readEncU32() + ")";
							break;
							
						case OpCode.OP_newclass:
							s += context.instances[code.readEncU32()]
							break;
							
						case OpCode.OP_lookupswitch:
							var pos:uint = code.position-1;
							var target:int = pos + code.readS24();
							var maxindex:int = code.readEncU32();
							s += "default:" + labels.labelFor(target) //// target + "("+(target-pos)+")"
							s += " maxcase:" + maxindex
							for (var j:int = 0; j <= maxindex; j++)
							{
								target = pos + code.readS24();
								s += " " + labels.labelFor(target) //// target + "("+(target-pos)+")"
							}
							break;
							
						case OpCode.OP_jump:
						case OpCode.OP_iftrue:
						case OpCode.OP_iffalse:
						case OpCode.OP_ifeq:
						case OpCode.OP_ifne:
						case OpCode.OP_ifge:
						case OpCode.OP_ifnge:
						case OpCode.OP_ifgt:
						case OpCode.OP_ifngt:
						case OpCode.OP_ifle:
						case OpCode.OP_ifnle:
						case OpCode.OP_iflt:
						case OpCode.OP_ifnlt:
						case OpCode.OP_ifstricteq:
						case OpCode.OP_ifstrictne:
							var offset:int = code.readS24();
							var target2:int = code.position + offset;
							////s += target2 + " ("+offset+")"
							s += labels.labelFor(target2)
							//if (!((code.position) in labels))
							//	s += "\n"
							break;
							
						case OpCode.OP_inclocal:
						case OpCode.OP_declocal:
						case OpCode.OP_inclocal_i:
						case OpCode.OP_declocal_i:
						case OpCode.OP_getlocal:
						case OpCode.OP_kill:
						case OpCode.OP_setlocal:
						case OpCode.OP_debugline:
						case OpCode.OP_getglobalslot:
						case OpCode.OP_getslot:
						case OpCode.OP_setglobalslot:
						case OpCode.OP_setslot:
						case OpCode.OP_pushshort:
						case OpCode.OP_newcatch:
							s += code.readEncU32();
							break
							
						case OpCode.OP_debug:
							s += code.readUnsignedByte()
							s += " " + code.readEncU32()
							s += " " + code.readUnsignedByte()
							s += " " + code.readEncU32()
							break;
							
						case OpCode.OP_newobject:
							s += "{" + code.readEncU32() + "}";
							break;
							
						case OpCode.OP_newarray:
							s += "[" + code.readEncU32() + "]";
							break;
							
						case OpCode.OP_call:
						case OpCode.OP_construct:
						case OpCode.OP_constructsuper:
							s += "(" + code.readEncU32() + ")";
							break;
						case OpCode.OP_pushbyte:
						case OpCode.OP_getscopeobject:
							s += code.readByte();
							break;
							
						case OpCode.OP_hasnext2:
							s += code.readEncU32() + " " + code.readEncU32();
							
						default:
							////if (OpCode.opNames[opcode] == ("0x"+opcode.toString(16).toUpperCase()))
							////	s += " UNKNOWN OPCODE"
							break
					}
					
					//var size:int = code.position - start
					
					
					//OpCode.totalSize += size
					//OpCode.opSizes[opcode] = int(OpCode.opSizes[opcode]) + size;
					
					sb.append(s + "\n");
				}
				
				sb.append(oldIndent + "}\n");
				
			}
			
			sb.append('/* end mip */');
			return sb;
		}
		private function cleanClass(s:String):String
		{
			var j:int;
			var rs:String = s;
			if ( (j = s.lastIndexOf('::')) > -1)
				rs = s.substr(j + 2 );
			if ( (j = s.lastIndexOf('>')) > -1)
				rs = 'Vector_' + rs.substr(0, rs.length - 1);
			return rs;
		}
		private function isPrivate(s:String):Boolean
		{
			return (s.indexOf('private::') == 0);
		}
	}
}

		